Conversation
* chore: Update POM to Java 25 and rename artifactId/groupId * update folder name from example to juv25d --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
* http parser * Bunny fixes. (only using input stream to recieve requests) * Bunny review improvements * Improved http parser ReadLine helper method to eliminate dependency on mark() and reset(). Implemented handleClient() using socket as a try-with-resources to avoid memory leakage in case of exception thrown by httpparser-methods. * NumberFormatException fix on line 53 -> 60 * chore: Update POM to Java 25 and rename artifactId/groupId (#11) * chore: Update POM to Java 25 and rename artifactId/groupId * update folder name from example to juv25d --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com> * resolve conflicts --------- Co-authored-by: Kristina <kristina0x7@gmail.com> Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
* Add ServerLogging.java as separate class for logging. Implement said class in SocketServer.java to return logging information upon opening socket and user connecting to server. * Update ServerLogging.java to include a static initializer block and an empty utility class to prevent instantiation. * Update ServerLogging.java to reference same class in getLogger argument. * Update ServerLogging.java to check if handler has already been instantiated and allow for log level to be set by args in JVM (default level 'INFO' if no args provided). * normalize logging statements to be consistent * remove unused imports * Update SockerServer.java to properly log server socket errors. --------- Co-authored-by: Mats Rönnqvist <mats.f.ronnqvist@gmail.com> Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
* update POM with pitest * add junit plugin dependency
Rebased 4 commits in this PR.
…#27) * Fix PiTest by defining argLine and removing invalid Mockito javaagent * self close argline
…edicated ConnectionHandler. (#28) * Rename SocketServer to Server Move HTTP request handling logic to a dedicated ConnectionHandler. * Convert createSocket to an instance method named start(). * Replace console prints with Logger in ConnectionHandler * Fixed typo in ConnectionHandler * refactor Server and ConnectionHandler: - Inject Logger inside of constructor - Inject handlerFactory into the Server that handles creation of a new ConnectionHandler on each request - Remove HttpParser from Server as it is not handling the parsing of a request * accept ConnectionHandlerFactory and not a specific implementation * normalize handlerFactory name --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
…tests. (#34) * Add testing for ServerLogging.java. Configure ServerLogging.java to improve its testability. * Update logger_shouldNotAddDuplicateHandlers test to properly test non-inclusion of duplicates. * Update ServerLogging.java to guard against invalid level string entries in configure method (logger.parse => setLevel).
* http parser * Bunny fixes. (only using input stream to recieve requests) * Bunny review improvements * Improved http parser ReadLine helper method to eliminate dependency on mark() and reset(). Implemented handleClient() using socket as a try-with-resources to avoid memory leakage in case of exception thrown by httpparser-methods. * NumberFormatException fix on line 53 -> 60 * Added foundation for Filters and Plugins. Added FilterChain to use created filters, and a Pipeline class to handle the workflow (Client → Filters → Plugin → Response → Client). Modified SocketServer handleClient() to use FilterChain. Added example code in App.java for Pipeline usage. TODO: Initialize HttpResponse class * Fixing build fail * add init and destroy to filter interface * add methods to pipeline class * Add servlet-style filter pipeline with lifecycle support * add documentation about temporary server impl * test: verify filters execute in order and plugin is called last * test: add coverage for filter blocking, lifecycle init, and empty pipeline * add loggingfiltertest * fix: construct HttpResponse with required arguments * fix: construct HttpResponse with required arguments * Update FilterChainImpl tests to use full HttpResponse constructor to ensure compatibility with future implementations * remove duplicated class --------- Co-authored-by: Kristina M <kristina0x7@gmail.com> Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
…39) * feat: make HttpResponse mutable and implement NotFoundPlugin default - Updated HttpResponse to be mutable to allow filters and plugins to modify responses. - Implemented NotFoundPlugin as a default fallback for the Pipeline. - Added null safety check in Pipeline.setPlugin. - Added unit tests for Pipeline default behavior and NotFoundPlugin. * ran mvn spotless:apply to fix violations * Rabbit comment fixes * Review fixes * Final buggfixes for this PR (again)
* Implement static file handler with security and tests Core Implementation: - Add StaticFileHandler for serving files from /resources/static/ - Add MimeTypeResolver for Content-Type detection - Add security validation to prevent path traversal attacks Testing: - Add MimeTypeResolverTest (15 test cases) - Add StaticFileHandlerTest (20+ test cases) - All tests passing Example Files: - Add index.html demo page with gradient styling - Add styles.css for professional styling - Add app.js for JavaScript functionality demo Note: Integration with Server/ConnectionHandler will be added after PR #28 merges to avoid conflicts. Foundation work for #18 * Introduce ADR structure and first ADR - Add ADR README explaining the ADR process for the team - Add TEMPLATE for writing future ADRs - Add ADR-001 documenting static file serving architecture Closes #16 * Rename SocketServer to Server Move HTTP request handling logic to a dedicated ConnectionHandler. (#28) * Rename SocketServer to Server Move HTTP request handling logic to a dedicated ConnectionHandler. * Convert createSocket to an instance method named start(). * Replace console prints with Logger in ConnectionHandler * Fixed typo in ConnectionHandler * refactor Server and ConnectionHandler: - Inject Logger inside of constructor - Inject handlerFactory into the Server that handles creation of a new ConnectionHandler on each request - Remove HttpParser from Server as it is not handling the parsing of a request * accept ConnectionHandlerFactory and not a specific implementation * normalize handlerFactory name --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com> * Add testing for ServerLogging.java. Configure ServerLogging.java for tests. (#34) * Add testing for ServerLogging.java. Configure ServerLogging.java to improve its testability. * Update logger_shouldNotAddDuplicateHandlers test to properly test non-inclusion of duplicates. * Update ServerLogging.java to guard against invalid level string entries in configure method (logger.parse => setLevel). * feature/FilterPlugin (#17) * http parser * Bunny fixes. (only using input stream to recieve requests) * Bunny review improvements * Improved http parser ReadLine helper method to eliminate dependency on mark() and reset(). Implemented handleClient() using socket as a try-with-resources to avoid memory leakage in case of exception thrown by httpparser-methods. * NumberFormatException fix on line 53 -> 60 * Added foundation for Filters and Plugins. Added FilterChain to use created filters, and a Pipeline class to handle the workflow (Client → Filters → Plugin → Response → Client). Modified SocketServer handleClient() to use FilterChain. Added example code in App.java for Pipeline usage. TODO: Initialize HttpResponse class * Fixing build fail * add init and destroy to filter interface * add methods to pipeline class * Add servlet-style filter pipeline with lifecycle support * add documentation about temporary server impl * test: verify filters execute in order and plugin is called last * test: add coverage for filter blocking, lifecycle init, and empty pipeline * add loggingfiltertest * fix: construct HttpResponse with required arguments * fix: construct HttpResponse with required arguments * Update FilterChainImpl tests to use full HttpResponse constructor to ensure compatibility with future implementations * remove duplicated class --------- Co-authored-by: Kristina M <kristina0x7@gmail.com> Co-authored-by: WHITEROSE <firasmoussa60@gmail.com> * Add charset=utf-8 to text-based Content-Type headers * Integrate StaticFileHandler with Pipeline architecture * Refactor to use immutable fields and clean up unused setters; update with usage notes for future integration. * Enhance StaticFilesPlugin to properly handle headers with map iteration; simplify and clarify class documentation. * Update tests to verify Content-Type headers include charset=utf-8 * connect StaticFilesPlugin --------- Co-authored-by: johanbriger <johanbriger@gmail.com> Co-authored-by: WHITEROSE <firasmoussa60@gmail.com> Co-authored-by: Mats Rönnqvist <203552386+bamsemats@users.noreply.github.com> Co-authored-by: Linus Westling <141355850+LinusWestling@users.noreply.github.com> Co-authored-by: Kristina M <kristina0x7@gmail.com>
* bump pom version for release * revert pom version * fix: docker release workflow to run on tag push * fix: update tag ref in docker release workflow * fix: update temurine version in DockerFile to match pom version 25 * fix: configure maven-jar-plugin to find the main class for manifest
* Add test for valid GET request and fix key case-sensitivity in header parsing Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify empty request throws exception Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify malformed request throws exception Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify valid query string Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify null and empty requests throws exception Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify malformed header line in get request throws exception Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify valid post request Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Verify invalid or negative content length throws exception Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Add test documentation and improve test readability for HttpParser Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Update tests and ensure UTF-8 handling; use case-insensitive headers in HttpParser Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Refactor: Introduce constant for "Content-Length" header in HttpParser --------- Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com>
…ons (#52) * add application properties and a ConfigLoader to load set configurations * integrate ConfigLoader into ServerLogging to read logging.level * inject Server port from ConfigLoader into constructor
* test: add global filter execution test * feat: implement global filter support with ordering * test: add route specific filter test * feat: add route specific filter matching logic * update main comments with upcoming filter impl * chore: run spotless:apply to fix unused imports and formatting * bunny-fix * refactor: optimize route filter lookup with Map structure * small fix * fix: make global filter cache thread-safe and immutable * implement the correct plugins so server works as intended and move comments to a doc --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
* Add IP filter skeleton with placeholder IP * Add IP filter and support client IP in HttpRequest * Add remote IP to HttpRequest in ConnectionHandler * Configured IP filter whitelist for localhost * Enable IP filter with open access for development * Update tests for HttpRequest remote IP failed * Document IP filter configuration in App * Resolved merge conflict in App pipeline configuration
…eadme.md (#63) * Update index.html to include a nav bar at the top, styles.css for adjustments to frontend aspects, add readme.html to host readme.md, basic javascript for loading markdown language content. * Update frontend logic to avoid errors loading data from readme.md. Smoothed out transitions between sites from navigation. * Update minor bugs and duplicate css code lines. * Update minor bugs and duplicate css code lines. * Update for sanitization of readme.md through DOMPurify. Added dependency min.js and its callback in index.html and readme.html. Removed superfluous markdown.js.
* Add Maven Shade Plugin for building a fat JAR; update README with detailed project overview and usage instructions * Update project version to 1.0.2-beta in pom.xml * Update README to reflect fat JAR packaging using Maven Shade Plugin * fix: specify final jar name so the DockerBuild picks the correct one, rollback pom version * rollback POM version to original * update README with dynamic tag and correct port number * update README with the dynamic specified jar file generated from maven-shaded --------- Co-authored-by: WHITEROSE <firasmoussa60@gmail.com>
* Refactor plugin handling by introducing `Router` abstraction; added `SimpleRouter` implementation. Updated pipeline and tests to support new routing system. * Refactor plugin handling by introducing `Router` abstraction; added `SimpleRouter` implementation. Updated pipeline and tests to support new routing system. * Improve wildcard routing in `SimpleRouter` with longest-prefix match logic; add test coverage for specific and wildcard match scenarios.
* add testcontainers dependency to pom.xml * adds 2 simple integration tests usiing Testcontainers to AppIT.java * changes from *.jar to app.jar in JAR path in Dockerfile to avoid copy conflict * changes AppIT.java according to code rabbit suggestion
* Add Bucket4j dependency to pom.xml for rate-limiting support Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Introduce RateLimitingFilter with Bucket4j for per-IP request throttling Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Work In Progress: Implement per-IP rate-limiting logic in RateLimitingFilter using Bucket4j and add response handling for rate limit exceeded * Finalize and integrate RateLimitingFilter with improved logging, validation, and server cleanup. Add to App pipeline and configure properties. Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Add unit tests for RateLimitingFilter to verify per-IP request handling, rate limit enforcement, and cleanup behavior Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Refactor RateLimitingFilter and tests: simplify comments in filter, improve test method naming, and add validation test Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Add Javadoc to RateLimitingFilter and its tests for improved clarity and documentation Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Remove StaticFilesPlugin from the App pipeline configuration Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Make RateLimitingFilter configuration dynamic and improve response handling in tests Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Add rate-limiting configuration to ConfigLoader and update App pipeline to use dynamic values Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Increase rate-limiting burst capacity to 100 in configuration properties Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> * Add configurable flag to enable/disable rate-limiting in App pipeline and ConfigLoader Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com> --------- Co-authored-by: Mattias Hagström <mattiashagstrommusic@gmail.com>
* Add `RedirectRule` class to handle URL redirection logic with support for exact and wildcard path matching
* Add `RedirectFilter` to handle HTTP URL redirection logic using configurable rules
* WIP: Save current work
* Fix HttpResponse empty constructor to initialize fields
Initialize headers map and body array in empty constructor to prevent
NullPointerException when setHeader() or setBody() are called.
Before:
HttpResponse() {} // headers = null, body = null
After:
HttpResponse() {
this.headers = new LinkedHashMap<>();
this.body = new byte[0];
}
This fix is required for RedirectFilter and any other code that creates
empty HttpResponse objects and modifies them using setters.
Fixes crashes in RedirectFilterTest.
* Improve wildcard matching in `RedirectRule` by using `Pattern.quote` for safer and more precise regex generation.
* Update `RedirectFilterTest` to include client IP address in test request creation
---------
Co-authored-by: Kristina M <kristina0x7@gmail.com>
* remove sourcemapping url comment from the JS file * remove stack trace from the 404 log message to console
* Add SecurityHeadersFilter for hardened HTTP responses * Add SecurityHeadersFilter for hardened HTTP responses * Changed X-XSS-Protection value to recommended 0, * address code review feedback from CodeRabbit * Add @global annotation to SecurityHeadersFilter for automatic registration * Removed line of code in App.java
…and LoggingFilter to use the logger, ensuring connection ID is included in the logs
…n ID is correctly included in the logs
…ad of System.out, reflecting recent changes to LoggingFilter
…ad of System.out, reflecting recent changes to LoggingFilter (#95)
📝 WalkthroughWalkthroughThis pull request introduces a complete, production-ready Java HTTP server implementation with multi-stage Docker builds, GitHub Actions CI/CD pipelines, comprehensive test coverage, and a web-based UI. The project includes request/response handling, filter chains, routing, static file serving, rate limiting, security headers, logging, and configuration management via YAML. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Server
participant ConnectionHandler
participant Pipeline
participant FilterChain as FilterChain<br/>(Global Filters)
participant RouterPlugins as Router & Plugins
Client->>Server: TCP connection on port 8080
Server->>ConnectionHandler: create Runnable via factory
Server->>ConnectionHandler: start on virtual thread
ConnectionHandler->>ConnectionHandler: parse InputStream to HttpRequest
ConnectionHandler->>Pipeline: createChain(HttpRequest)
Pipeline->>FilterChain: build with sorted global filters<br/>+ route-specific filters
ConnectionHandler->>FilterChain: doFilter(request, response)
loop Global Filters (SecurityHeaders, Logging, RateLimit, etc.)
FilterChain->>FilterChain: execute next filter in sequence
Note over FilterChain: Each filter may<br/>modify request/response
end
FilterChain->>RouterPlugins: chain exhausted, resolve(request)
RouterPlugins->>RouterPlugins: match exact path or wildcard
RouterPlugins->>RouterPlugins: invoke plugin.handle(request, response)
Note over RouterPlugins: e.g., StaticFilesPlugin,<br/>NotFoundPlugin
RouterPlugins-->>FilterChain: plugin populates response
FilterChain-->>ConnectionHandler: response ready
ConnectionHandler->>ConnectionHandler: HttpResponseWriter.write(out, response)
ConnectionHandler-->>Client: HTTP response with headers & body
ConnectionHandler->>ConnectionHandler: cleanup LogContext
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (33)
docs/notes/pipeline-usage.md-32-35 (1)
32-35:⚠️ Potential issue | 🟡 MinorPriority collision:
RateLimitFilterandJwtAuthFiltershare priority 100 on/api/*Both filters are documented with the same priority (100) for the
/api/*path pattern:pipeline.addRouteFilter(new RateLimitFilter(1000, 60000), 100, "/api/*"); // priority 100 pipeline.addRouteFilter(new JwtAuthFilter(), 100, "/api/*"); // priority 100Same-priority filters on the same route create undefined execution order. The typical intent is rate-limiting before auth (so unauthenticated requests still consume their rate-limit budget and expensive auth logic isn't invoked for blocked clients). If your
Pipelineuses stable insertion-order tie-breaking this may work today, but it's a fragile assumption. Consider separating the priorities explicitly, for example:- // pipeline.addRouteFilter(new RateLimitFilter(1000, 60000), 100, "/api/*"); + // pipeline.addRouteFilter(new RateLimitFilter(1000, 60000), 90, "/api/*"); // runs before auth // pipeline.addRouteFilter(new JwtAuthFilter(), 100, "/api/*");A brief comment explaining the priority ordering convention (lower = earlier vs. higher = earlier) would also prevent future misconfigurations.
Also applies to: 48-50
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/notes/pipeline-usage.md` around lines 32 - 35, Two filters (RateLimitFilter and JwtAuthFilter) are shown with the same priority (100) for the same route pattern ("/api/*"), causing undefined ordering; change the pipeline.addRouteFilter calls so RateLimitFilter has a higher precedence than JwtAuthFilter (e.g., set RateLimitFilter priority to run before JwtAuthFilter) and add a short comment near the pipeline registration documenting the priority convention used (explicitly state whether lower numbers run earlier or later) to avoid future collisions; update both occurrences referenced (the /api/* registrations around the earlier and later blocks) to use distinct priorities and the new comment.docs/notes/pipeline-usage.md-25-25 (1)
25-25:⚠️ Potential issue | 🟡 MinorClarify or remove the
"/admin/**"recursive wildcard caveatThe
(if implemented)qualifier signals that this pattern may silently not work in the current router. A developer who uncomments a filter using"/admin/**"expecting recursive matching could end up with either no matches or subtly wrong behavior.Either confirm implementation and remove the qualifier, or replace the example with a supported pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/notes/pipeline-usage.md` at line 25, The docs note uses the ambiguous pattern "/admin/**" with an "(if implemented)" caveat which risks misleading callers; either confirm and remove the qualifier by updating the docs to assert that "/admin/**" is supported, or replace the example with a supported pattern (e.g., "/admin/*" or whatever the router actually matches) and remove the parenthetical; locate the string "/admin/**" in pipeline-usage.md and update the example and surrounding sentence so it accurately reflects the router's implemented matching behavior.Dockerfile-15-17 (1)
15-17:⚠️ Potential issue | 🟡 MinorAdd an
EXPOSEdirective and resolve the hardcoded JAR nameTwo minor gaps in the runtime stage:
- No
EXPOSEinstruction — the listening port isn't self-documented in the image. AddEXPOSE <port>to signal the expected port to tooling and operators.- The hardcoded
app.jarpath (acknowledged in the TODO comment) must match thefinalNameinpom.xml(e.g., from the Maven Shade plugin). If they diverge theCOPYwill silently succeed but the entrypoint will fail at runtime withUnable to access jarfile app.jar.Would you like me to verify the
finalNameconfigured in yourpom.xmland generate the correctedCOPYpath?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Dockerfile` around lines 15 - 17, Add an EXPOSE line to document the service port (e.g., EXPOSE 8080 or the actual port your app listens on) and make the runtime-stage JAR path consistent with your Maven build: check the Maven pom.xml (finalName or the Shade plugin’s output name) and update the COPY --from=build source to that exact artifact (or make ENTRYPOINT reference the artifact name you copy) so the jar path used by COPY and ENTRYPOINT match and avoid "Unable to access jarfile app.jar" at runtime..github/workflows/ci.yml-14-14 (1)
14-14:⚠️ Potential issue | 🟡 Minor
actions/checkout@v5is outdated and inconsistent withdocker-release.ymlThe current latest version of
actions/checkoutisv6, while this workflow pinsv5. The Docker release workflow in this same PR usesactions/checkout@v6.0.2, creating an inconsistency between the two CI workflows.🔧 Proposed fix
- uses: actions/checkout@v5 + uses: actions/checkout@v6🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci.yml at line 14, The workflow pins actions/checkout@v5 which is outdated and inconsistent with the other CI workflow; update the checkout action in the CI workflow by replacing actions/checkout@v5 with the current version used elsewhere (e.g., actions/checkout@v6.0.2) so both CI and docker-release workflows use the same actions/checkout version; ensure any required inputs remain compatible with the newer action.src/main/resources/static/README.md-134-145 (1)
134-145:⚠️ Potential issue | 🟡 MinorPort mismatch between README and configuration.
The README documents port 3000 (lines 134, 143, 145, 163), but
src/main/resources/application-properties.ymlconfiguresport: 8080. One of these needs to be updated to match the other to avoid confusing users.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/README.md` around lines 134 - 145, The README documents the app running on port 3000 while the application config sets port: 8080, causing a mismatch; pick the intended runtime port and make them consistent by either updating the README to show the configured port (change documented URLs and docker run -p mapping from 3000 to 8080) or updating the application configuration (change port: 8080 to port: 3000) so the documented URL and docker run port mapping match the configured server port; ensure references to the port value (e.g., the docker run -p mapping and the README URL) and the application property "port: 8080" are updated together.src/main/java/org/juv25d/http/HttpParser.java-61-61 (1)
61-61:⚠️ Potential issue | 🟡 Minor
readNBytesmay return fewer bytes than requested without error.If the connection is closed prematurely,
readNBytes(length)returns a shorter array silently. The parsedHttpRequestwould then contain a truncated body with no indication of the problem. Consider validatingbody.length == length.Proposed fix
body = in.readNBytes(length); + if (body.length != length) { + throw new IOException("Incomplete body: expected " + length + " bytes, got " + body.length); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/http/HttpParser.java` at line 61, The call to in.readNBytes(length) in HttpParser can return fewer bytes than requested if the connection closes; update the parsing code that assigns body to validate that body.length == length after the read, and treat a short read as an error: throw or return a failed parse (e.g., IllegalStateException or a ParseException) and/or log and close the connection. Locate the assignment to body (body = in.readNBytes(length)) in class HttpParser and add a check for the expected length, handling the mismatch by failing the request parse rather than accepting a truncated body.src/main/resources/static/readme.html-18-18 (1)
18-18:⚠️ Potential issue | 🟡 MinorHTML entities missing terminating semicolons.
🔍and🔎should be🔍and🔎. Without the semicolons, these are technically malformed character references per the HTML5 spec and may not render in strict parsers.🔧 Proposed fix
- <h1>🔍 ReadMe 🔎</h1> + <h1>🔍 ReadMe 🔎</h1>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/readme.html` at line 18, The HTML character references in the H1 element are missing terminating semicolons; update the H1 text node that currently contains "🔍" and "🔎" to use "🔍" and "🔎" respectively so the character references are well-formed (locate the <h1> element containing these entities and add the semicolons).src/main/resources/static/readme.html-37-37 (1)
37-37:⚠️ Potential issue | 🟡 MinorInconsistent script path — missing leading
/onpurify.min.js.All other static assets on this page use root-relative paths (
/js/...,/css/...), butpurify.min.jsuses a bare-relative path. While functionally identical when served from/, it will silently fail if the file is ever moved or served from a subdirectory.🔧 Proposed fix
-<script src="js/purify.min.js"></script> +<script src="/js/purify.min.js"></script>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/readme.html` at line 37, Update the script src for purify.min.js to use a root-relative path so it matches the other static asset URLs; locate the <script> tag that references "purify.min.js" and change its src from "js/purify.min.js" to "/js/purify.min.js" to avoid breakage when the site is served from a subdirectory.src/test/java/org/juv25d/plugin/NotFoundPluginTest.java-24-24 (1)
24-24:⚠️ Potential issue | 🟡 MinorSpecify charset explicitly in
getBytes().
"404 - Resource Not Found".getBytes()usesCharset.defaultCharset(). If the JVM default is not UTF-8, this assertion can produce a false positive or false negative. Pair it with the same fix inNotFoundPlugin.java.🔧 Proposed fix
- assertArrayEquals("404 - Resource Not Found".getBytes(), res.body()); + assertArrayEquals("404 - Resource Not Found".getBytes(StandardCharsets.UTF_8), res.body());Also add the import at the top of the file:
+import java.nio.charset.StandardCharsets;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/plugin/NotFoundPluginTest.java` at line 24, Update the assertion in NotFoundPluginTest to use an explicit charset rather than the platform default: change the hardcoded byte comparison to use "404 - Resource Not Found".getBytes(StandardCharsets.UTF_8) and add the corresponding import for java.nio.charset.StandardCharsets; also make the analogous change in NotFoundPlugin (where the response body is produced) to use StandardCharsets.UTF_8 so both sides use the same encoding for res.body() comparisons.src/main/java/org/juv25d/plugin/NotFoundPlugin.java-13-13 (1)
13-13:⚠️ Potential issue | 🟡 MinorSpecify charset in
getBytes().
"404 - Resource Not Found".getBytes()usesCharset.defaultCharset(), which is platform-dependent. This can produce unexpected bytes on non-UTF-8 JVMs, and creates a fragile pairing with the test assertion inNotFoundPluginTest.🔧 Proposed fix
+import java.nio.charset.StandardCharsets; ... - res.setBody("404 - Resource Not Found".getBytes()); + res.setBody("404 - Resource Not Found".getBytes(StandardCharsets.UTF_8));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/plugin/NotFoundPlugin.java` at line 13, The response body in NotFoundPlugin uses platform-default bytes via "404 - Resource Not Found".getBytes(); change this to use an explicit charset (e.g., StandardCharsets.UTF_8) so the bytes are deterministic across platforms and match tests: update the call in NotFoundPlugin (where res.setBody(...) is invoked) to encode the string with StandardCharsets.UTF_8.src/test/java/org/juv25d/logging/ConnectionIdLoggingTest.java-33-47 (1)
33-47:⚠️ Potential issue | 🟡 MinorAdd
logger.removeHandler(handler)to thefinallyblock to prevent handler accumulation.
Logger.getLogger("test.connectionid")returns a shared, JVM-scoped instance. Without removing the handler, each test execution appends a new handler to the same logger. In parallel or repeated test-suite runs this causes handler leakage — prior run's handlers still capture records — making the test flaky.🛡️ Proposed fix
try { String testId = "test-123"; LogContext.setConnectionId(testId); // Act logger.info("This is a test message"); // Assert assertTrue(formattedMessages.get(0).contains("[" + testId + "]"), "Log message should contain the connection ID. Found: " + formattedMessages.get(0)); } finally { + logger.removeHandler(handler); LogContext.clear(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/logging/ConnectionIdLoggingTest.java` around lines 33 - 47, The test is leaking handlers on the shared Logger instance; modify the finally block in ConnectionIdLoggingTest so it not only calls LogContext.clear() but also removes the test handler from the logger by calling logger.removeHandler(handler) to prevent handler accumulation across test runs; locate the try/finally around LogContext.setConnectionId/test logging and ensure you remove the same `handler` that was added to `logger` before clearing the context so subsequent tests don't receive stale records from `formattedMessages`.src/main/java/org/juv25d/plugin/HelloPlugin.java-11-13 (1)
11-13:⚠️ Potential issue | 🟡 MinorPlaceholder
handle()produces an uninitializedHttpResponse(statusCode0,statusTextnull).Until the body is implemented, any request routed to
HelloPluginwill emit a malformed HTTP response. Even for scaffolding, a minimal fallback (e.g.,200 OKwith a placeholder body) prevents breaking the server during integration testing.💡 Minimal placeholder implementation
`@Override` public void handle(HttpRequest req, HttpResponse res) throws IOException { - // placeholder response logic + res.setStatusCode(200); + res.setStatusText("OK"); + res.setBody("Hello, World!".getBytes(java.nio.charset.StandardCharsets.UTF_8)); + res.setHeader("Content-Type", "text/plain; charset=UTF-8"); }Would you like me to generate a more complete implementation for this plugin?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/plugin/HelloPlugin.java` around lines 11 - 13, The handle(HttpRequest req, HttpResponse res) method in HelloPlugin currently leaves HttpResponse uninitialized, producing statusCode 0 and statusText null; update HelloPlugin.handle to set a minimal fallback response (e.g., set status code to 200, status text to "OK", appropriate Content-Type, and a small placeholder body like "Hello from HelloPlugin") and ensure headers and body length are correctly set on res so any routed request returns a well-formed HTTP response during integration tests.src/main/resources/static/css/styles.css-63-132 (2)
63-132:⚠️ Potential issue | 🟡 Minor
declaration-empty-line-beforeStylelint errors throughout.nav-menublock — multiple[error]violations.Stylelint 17.3.0 flags empty lines immediately before declarations on lines 65, 72, 88, 90–105. These should be resolved by removing the blank lines or adjusting the block-level comment placement to separate concerns without triggering the rule.
The most impactful instances (lines 72, 88, 90–105) can be addressed by moving
anchor-nameandposition-anchorto immediately follow the previous declaration:📐 Representative fix for lines 63–75
.nav-menu { - width: fit-content; margin: 0.4rem auto 1rem auto; display: flex; align-content: center; justify-content: center; isolation: isolate; - anchor-name: --hovered-link;Similar blank-line removal is needed at lines 88, 183, 212, 226, 228, 230–231, and 246 per the full Stylelint report.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/css/styles.css` around lines 63 - 132, Stylelint is flagging empty lines before declarations inside the .nav-menu block; remove the blank lines that precede declarations (or reposition block-level comments) so declarations are contiguous—specifically move anchor-name: --hovered-link; and position-anchor: --hovered-link; to immediately follow the preceding declaration in the .nav-menu rule and in the &::before / &::after rules, and eliminate the stray blank lines before other properties in ::before, ::after and &:has(a:hover)::before / ::after so no declaration is separated by an empty line.
63-132:⚠️ Potential issue | 🟡 MinorNav hover effect uses CSS Anchor Positioning without an
@supportsguard.The
.nav-menuand.featuresblocks useanchor-name,position-anchor, andanchor()(lines 72, 75, 88, 122–125, 213, 215, 228) without@supportsguards. As of February 2026,anchor-nameand theanchor()function are Baseline Newly available, butposition-anchorremains in limited availability. In Firefox, the feature is enabled by default only in version 147+; Firefox 145–146 requires a flag. Wrapping these declarations in@supports (position-anchor: top)or similar (as Chrome's documentation recommends) makes the intent explicit and future-proof, ensuring the decorative hover effect gracefully degrades in unsupported browsers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/css/styles.css` around lines 63 - 132, Wrap all anchor positioning properties and anchor() usage inside a feature-detection block so they gracefully degrade: add an `@supports` (position-anchor: top) guard around the rules in .nav-menu that use anchor-name, position-anchor, and the anchor() function (including the li:hover anchor-name, the &::before/&::after top/left/right/bottom values that call anchor(), and the &:has(a:hover) block) so those declarations only apply when position-anchor is supported and browsers without it ignore them.src/main/resources/static/css/styles.css-108-115 (1)
108-115:⚠️ Potential issue | 🟡 MinorStylelint
comment-whitespace-insideerrors on lines 111 and 156 — add spaces inside comment delimiters.Stylelint reports these as
[error]-level violations.🛠️ Proposed fix
- /*backdrop-filter: blur(2px);*/ + /* backdrop-filter: blur(2px); */- }*/ + } */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/css/styles.css` around lines 108 - 115, Stylelint flags comment-whitespace-inside for comments inside the &::before and &::after blocks; update inline CSS comments to include a single space after /* and before */ (e.g., change /*backdrop-filter: blur(2px);*/ to /* backdrop-filter: blur(2px); */) and apply the same spacing convention to the other commented line referenced in the &::after block so both comments conform to the comment-whitespace-inside rule.src/main/resources/static/index.html-47-47 (1)
47-47:⚠️ Potential issue | 🟡 MinorMissing leading
/inpurify.min.jsscript path.All other static asset references (
/css/styles.css,/js/marked.min.js,/js/app.js) use absolute paths. This one uses a relative path, which works when served from/but silently breaks under any sub-path routing or redirect.🐛 Proposed fix
-<script src="js/purify.min.js"></script> +<script src="/js/purify.min.js"></script>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/resources/static/index.html` at line 47, The script tag referencing purify.min.js in index.html uses a relative path "js/purify.min.js" which breaks under sub-path routing; update the script's src attribute so it uses an absolute path starting with a leading slash (consistent with the other assets like /css/styles.css and /js/app.js) to point to /js/purify.min.js.src/main/java/org/juv25d/handler/StaticFileHandler.java-16-22 (1)
16-22:⚠️ Potential issue | 🟡 MinorJavadoc describes the resource root as
/resources/static/but the code maps to/static/.
STATIC_DIR = "/static/"is the correct classpath path (Maven strips thesrc/main/resourcesprefix). The Javadoc examples (GET / → /resources/static/index.html) are misleading.📝 Suggested correction
- * Handles serving static files from the /resources/static/ directory. + * Handles serving static files from the classpath path /static/ + * (corresponding to src/main/resources/static/ in the Maven source tree). * <p> * URL mapping: - * - GET / → /resources/static/index.html - * - GET /about.html → /resources/static/about.html - * - GET /css/styles.css → /resources/static/css/styles.css + * - GET / → /static/index.html + * - GET /about.html → /static/about.html + * - GET /css/styles.css → /static/css/styles.css🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/handler/StaticFileHandler.java` around lines 16 - 22, The Javadoc incorrectly refers to "/resources/static/" while the code uses STATIC_DIR = "/static/"; update the Javadoc in StaticFileHandler to reference the actual classpath root "/static/" and adjust the URL mapping examples (e.g., "GET / → /static/index.html", "GET /about.html → /static/about.html", "GET /css/styles.css → /static/css/styles.css") so they match the STATIC_DIR constant and real runtime behavior.src/test/java/org/juv25d/handler/StaticFileHandlerTest.java-13-20 (1)
13-20:⚠️ Potential issue | 🟡 MinorTests asserting 200 depend on actual classpath resources—brittle and prone to silent failures.
shouldReturn200ForExistingFile,shouldReturnCorrectContentTypeForHtml/Css/Js,shouldServeIndexHtmlForRootPath, andshouldReturnNonEmptyBodyForSuccessfulRequestall invokeStaticFileHandler.handleand assert 200 responses. They depend on real files atsrc/main/resources/static/index.html,css/styles.css, andjs/app.js. If these files are deleted or renamed, tests will fail silently with 404 assertions rather than signaling test framework errors.Use either (a) test-only resource fixtures under
src/test/resources/static/, or (b) mock the resource loading path to decouple tests from filesystem state.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/handler/StaticFileHandlerTest.java` around lines 13 - 20, The tests (shouldReturn200ForExistingFile, shouldReturnCorrectContentTypeForHtml/Css/Js, shouldServeIndexHtmlForRootPath, shouldReturnNonEmptyBodyForSuccessfulRequest) are brittle because they rely on real classpath files; update the tests to use test fixtures or mocking: either add the required files (index.html, css/styles.css, js/app.js) under src/test/resources/static/ so StaticFileHandler.handle(request) finds them during tests, or refactor the handler to allow injecting a ResourceLoader/ClasspathResolver and in tests mock that dependency to return controlled InputStreams for those paths; make the change against the test methods and StaticFileHandler so assertions asserting 200 no longer depend on production resources.src/test/java/org/juv25d/handler/StaticFileHandlerTest.java-60-62 (1)
60-62:⚠️ Potential issue | 🟡 Minor
new String(response.body())without specifying charset.The response body is UTF-8 encoded (set explicitly in
StaticFileHandler). Relying on the platform default charset makes these assertions non-deterministic on systems where the default is not UTF-8.♻️ Proposed fix (applies to lines 61, 62, and 72)
- assertThat(new String(response.body())).contains("404"); - assertThat(new String(response.body())).contains("Not Found"); + assertThat(new String(response.body(), StandardCharsets.UTF_8)).contains("404"); + assertThat(new String(response.body(), StandardCharsets.UTF_8)).contains("Not Found");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/handler/StaticFileHandlerTest.java` around lines 60 - 62, In StaticFileHandlerTest update the assertions that build Strings from response.body() to specify UTF-8 explicitly (e.g., replace occurrences of new String(response.body()) with new String(response.body(), StandardCharsets.UTF_8)) so the tests are deterministic; ensure you import java.nio.charset.StandardCharsets and apply this change for the assertions checking "404", "Not Found" and any other place (line with 72) that converts response.body() to a String.src/main/java/org/juv25d/filter/RateLimitingFilter.java-109-118 (1)
109-118:⚠️ Potential issue | 🟡 MinorClient IP leaked in 429 response body.
Line 110 includes the client's IP address in the HTTP response body. This is a minor information disclosure — the client (or any intermediary) learns how the server perceives their IP. Remove the IP from the user-visible response and keep it in the server-side log only (which you already do in
logRateLimitExceeded).Proposed fix
- byte[] body = ("429 Too Many Requests: Rate limit exceeded for IP " + ip + "\n") + byte[] body = "429 Too Many Requests: Rate limit exceeded\n" .getBytes(StandardCharsets.UTF_8);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/filter/RateLimitingFilter.java` around lines 109 - 118, The response body in sendTooManyRequests currently leaks the client's IP; change the user-visible message to a generic text (e.g., "429 Too Many Requests: Rate limit exceeded") and remove the ip variable from the body while leaving headers/status intact; keep the existing logRateLimitExceeded call (or ensure server-side logging still records the ip) so the IP is only present in server logs, not in the HTTP response body.src/main/java/org/juv25d/filter/RateLimitingFilter.java-83-91 (1)
83-91:⚠️ Potential issue | 🟡 MinorUse the builder API instead of the deprecated
Bandwidth.classic()factory.The project uses Bucket4j 8.16.1. While
Refill.intervally()is still supported,Bandwidth.classic()is deprecated in 8.x. Replace the deprecated pattern with the builder API:private Bucket createBucket() { return Bucket.builder() .addLimit(limit -> limit .capacity(capacity) .refillIntervally(refillTokens, refillPeriod)) .build(); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/filter/RateLimitingFilter.java` around lines 83 - 91, Replace the deprecated Bandwidth.classic usage inside createBucket(): stop creating a Bandwidth via Bandwidth.classic(capacity, Refill.intervally(...)) and instead use the Bucket.builder().addLimit(...) builder API; inside addLimit use the lambda to set capacity(capacity) and refillIntervally(refillTokens, refillPeriod) (instead of Refill.intervally), then build the Bucket. Update references to Bandwidth.classic and keep the existing refillTokens, refillPeriod and capacity variables.docs/adr/ADR-001-static-file-serving-architecture.md-4-4 (1)
4-4:⚠️ Potential issue | 🟡 MinorADR status should be updated to reflect current state.
The implementation described in this ADR already exists in the PR (StaticFileHandler, MimeTypeResolver, tests, etc.). Update the status from
ProposedtoAccepted(orImplemented) so the ADR accurately reflects the decision lifecycle.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/adr/ADR-001-static-file-serving-architecture.md` at line 4, Update the ADR status line in ADR-001-static-file-serving-architecture.md from "Proposed" to "Accepted" (or "Implemented") to reflect the actual implementation; ensure the document header or status field is changed and optionally add a brief note that StaticFileHandler, MimeTypeResolver and accompanying tests have been implemented so the ADR accurately matches the existing codebase.README.md-39-39 (1)
39-39:⚠️ Potential issue | 🟡 MinorMultiple fenced code blocks are missing a language identifier (markdownlint MD040).
Lines 39, 141, 156, 162, 174, 269, 322, 364, 370, and 386 open triple-backtick blocks without a language hint. Use
textorplaintextfor diagrams/paths andbash/yaml/javafor the appropriate blocks to silence these MD040 warnings.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 39, Several fenced code blocks in README.md are missing language identifiers (MD040); open triple-backtick blocks at the noted locations should be updated to include an appropriate language tag. Edit each offending block (the triple-backtick fences) and add a language hint such as text or plaintext for diagrams/paths, bash for shell commands, yaml for YAML snippets, or java for Java code so the fences (```...) become ```text, ```bash, ```yaml, etc., to silence the markdownlint MD040 warnings.docs/adr/ADR-001-static-file-serving-architecture.md-238-238 (1)
238-238:⚠️ Potential issue | 🟡 MinorReplace placeholder URL with the actual repository link.
Line 238 contains
https://github.com/your-repo/issues/18. Update to the real repository URL before merging.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/adr/ADR-001-static-file-serving-architecture.md` at line 238, Replace the placeholder repository link "https://github.com/your-repo/issues/18" used in the markdown link "[Issue `#18`: GET handling for static files]" with the actual repository issue URL (e.g., "https://github.com/ORG/REPO/issues/18") so the reference resolves correctly; update the single occurrence of that placeholder URL in ADR-001-static-file-serving-architecture.md to the real repo path before merging.README.md-178-178 (1)
178-178:⚠️ Potential issue | 🟡 MinorHeading levels skip from
h1toh3(markdownlint MD001).Lines 178 (
### Example Mapping) and 251 (### Supported Patterns) follow anh1section directly — violating the one-level-at-a-time increment rule. Promote the section above to##or demote the subsection to##to fix both.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 178, The README uses level-3 headings "### Example Mapping" and "### Supported Patterns" directly after an h1, causing markdownlint MD001; update those headings to level-2 (change "### Example Mapping" and "### Supported Patterns" to "## Example Mapping" and "## Supported Patterns") so the document increments heading levels one at a time.src/main/java/org/juv25d/filter/RedirectRule.java-30-35 (1)
30-35:⚠️ Potential issue | 🟡 MinorMissing null validation for
sourcePathandtargetUrlconstructor parameters.If either is
null,sourcePath.contains("*")inmatches()(orgetTargetUrl()usage inRedirectFilter) will NPE at runtime. A simple guard in the constructor prevents this class of bugs:🛡️ Proposed fix
public RedirectRule(String sourcePath, String targetUrl, int statusCode) { validateStatusCode(statusCode); + Objects.requireNonNull(sourcePath, "sourcePath must not be null"); + Objects.requireNonNull(targetUrl, "targetUrl must not be null"); this.sourcePath = sourcePath; this.targetUrl = targetUrl; this.statusCode = statusCode; }Add
import java.util.Objects;to the imports.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/filter/RedirectRule.java` around lines 30 - 35, The RedirectRule constructor lacks null checks for sourcePath and targetUrl which can cause NPEs in matches() (sourcePath.contains("*")) and when getTargetUrl() is used; update the RedirectRule(String sourcePath, String targetUrl, int statusCode) constructor to validate non-null inputs (e.g., using Objects.requireNonNull or equivalent) for sourcePath and targetUrl before assigning fields and still call validateStatusCode(statusCode); add the necessary import (java.util.Objects) if you use Objects.requireNonNull and ensure error messages clearly state which parameter is null.src/main/java/org/juv25d/util/ConfigLoader.java-9-12 (1)
9-12:⚠️ Potential issue | 🟡 MinorMissing field defaults when YAML sections are absent.
If the
serverorloggingsection is entirely absent fromapplication-properties.yml, the null-guardif (serverConfig != null)silently skips the block, leavingport = 0,rootDirectory = null, andlogLevel = null.port = 0causes the server to bind to a random ephemeral port;nullvalues will cause NPEs downstream inServerLoggingand static-file serving.Initialize the fields to sensible defaults at declaration time so they remain correct even when the YAML section is omitted:
🛡️ Proposed fix
- private int port; - private String logLevel; - private String rootDirectory; + private int port = 8080; + private String logLevel = "INFO"; + private String rootDirectory = "static";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/util/ConfigLoader.java` around lines 9 - 12, The ConfigLoader class currently leaves port, logLevel, and rootDirectory uninitialized when YAML sections are missing, causing port=0 and null NPEs; initialize the fields at declaration in ConfigLoader (port, logLevel, rootDirectory) to sensible defaults (e.g., port 8080, logLevel "INFO", rootDirectory "./public" or another project default) so that when serverConfig or logging maps are null the rest of the code (including ServerLogging and static-file serving) has safe fallback values; update any related null-guards in the init logic to only override these defaults when values are present in the parsed serverConfig/logging maps.src/main/java/org/juv25d/http/HttpResponseWriter.java-40-41 (1)
40-41:⚠️ Potential issue | 🟡 Minor
Content-Lengthis always emitted, including for status codes that forbid it.RFC 7230 §3.3.2 prohibits a server from sending
Content-Lengthin204 No Contentand304 Not Modifiedresponses (and 1xx responses). The current implementation unconditionally writesContent-Length: 0for these codes, which is a protocol violation that some strict clients or proxies may reject.🛡️ Suggested guard
+ private static final java.util.Set<Integer> NO_BODY_STATUSES = + java.util.Set.of(204, 304); private static void writeHeaders( OutputStream out, Map<String, String> headers, byte[] body ) throws IOException { for (Map.Entry<String, String> header : headers.entrySet()) { if (!header.getKey().equalsIgnoreCase("Content-Length")) { String line = header.getKey() + ": " + header.getValue() + "\r\n"; out.write(line.getBytes(StandardCharsets.UTF_8)); } } - String contentLength = "Content-Length: " + body.length + "\r\n"; - out.write(contentLength.getBytes(StandardCharsets.UTF_8)); + // Omit Content-Length for status codes that forbid a message body + // (check is done on caller side by passing statusCode here, or gate in write()) out.write("\r\n".getBytes(StandardCharsets.UTF_8)); }The cleanest approach is to pass
response.statusCode()intowriteHeadersand skip the Content-Length line when the code is 204 or 304.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/http/HttpResponseWriter.java` around lines 40 - 41, The HttpResponseWriter currently always writes a Content-Length header using the body byte array; change writeHeaders (or the method that writes headers in HttpResponseWriter) to accept the response status code (or read response.statusCode()) and skip emitting the "Content-Length" line when the status is 1xx, 204, or 304 (i.e., do not write "Content-Length: 0" for those codes); keep emitting Content-Length for all other statuses and use the existing body.length logic unchanged for those cases.docs/adr/ADR-001-static-file-serving-architecture.md-46-46 (1)
46-46:⚠️ Potential issue | 🟡 MinorAdd language hints to fenced code blocks (markdownlint MD040).
Lines 46, 65, and 224 all open fenced code blocks without a language identifier. markdownlint-cli2 reports
MD040warnings for each. Usetextorplaintextfor ASCII-art diagrams to silence these.🔧 Suggested fixes
-``` +```text src/main/resources/(Same fix applies to lines 65 and 224.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/adr/ADR-001-static-file-serving-architecture.md` at line 46, Several fenced code blocks in the ADR document open without a language identifier (the ASCII-art directory/listing blocks); update each of those fenced blocks (the three places that show plain text/ASCII diagrams) to use a language hint such as ```text or ```plaintext so markdownlint MD040 is satisfied—replace the opening ``` with ```text (e.g., ```text followed by src/main/resources/) for all three occurrences.src/test/java/org/juv25d/http/HttpParserTest.java-153-153 (1)
153-153:⚠️ Potential issue | 🟡 MinorUse explicit charset in
body.getBytes()for platform independence.
body.getBytes()relies on the platform default charset. While"body"is ASCII-safe today, the assertion should be deterministic regardless of JVM locale.🛡️ Proposed fix
- assertThat(result.body()).isEqualTo(body.getBytes()); + assertThat(result.body()).isEqualTo(body.getBytes(StandardCharsets.UTF_8));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/http/HttpParserTest.java` at line 153, The assertion in HttpParserTest uses body.getBytes() which is platform-dependent; update the assertion to use an explicit charset (e.g., StandardCharsets.UTF_8) so the comparison is deterministic across JVM locales—modify the assertion that references result.body() and body.getBytes() to call body.getBytes(StandardCharsets.UTF_8) and add the necessary import for StandardCharsets if missing.src/test/java/org/juv25d/logging/ServerLoggingTest.java-84-90 (1)
84-90:⚠️ Potential issue | 🟡 MinorTest assumes the configured log level is INFO.
This test asserts
Level.INFOon the singleton logger, but the actual level is determined byapplication-properties.yml(or env/system property). If the config specifies a different level, this test breaks. Consider either reading the expected level from the config or testing a freshly configured logger with a known level.src/test/java/org/juv25d/logging/ServerLoggingTest.java-123-141 (1)
123-141:⚠️ Potential issue | 🟡 MinorAdd null check for
ConfigLoader.getLogLevel()to prevent NPE if configuration is missing.The
logLevelfield inConfigLoaderis not initialized with a default value. If thelogging.levelkey is absent fromapplication-properties.yml, line 131 will throwNullPointerExceptiononconfigLogLevel.toUpperCase(). While the current config file includes the key, the test should be defensive against configuration changes.Proposed fix
void logger_shouldUseLogLevelFromApplicationProperties() { String configLogLevel = ConfigLoader.getInstance().getLogLevel(); + assertNotNull(configLogLevel, "Expected logging.level to be set in application-properties.yml"); try { System.clearProperty("log.level");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/org/juv25d/logging/ServerLoggingTest.java` around lines 123 - 141, The test should defensively handle a missing logging.level by checking ConfigLoader.getInstance().getLogLevel() for null/blank before calling toUpperCase(); in the test method logger_shouldUseLogLevelFromApplicationProperties(), replace the direct use of configLogLevel with a guarded value (e.g., if configLogLevel is null or empty set it to "INFO") and then call Level.parse on that guarded string, leaving the rest of the flow (Logger testLogger, ServerLogging.configure(testLogger), assertEquals) unchanged so no NPE occurs when the config key is absent.src/main/java/org/juv25d/Pipeline.java-43-64 (1)
43-64:⚠️ Potential issue | 🟡 Minor
createChain()should validate thatrouteris set before use.While
setRouter()validates non-null input,createChain()passesrouterdirectly toFilterChainImplwithout checking if it has been set. This creates an API design gap: ifcreateChain()is called beforesetRouter(), aNullPointerExceptionwill occur inFilterChainImpl.doFilter()(line 27) whenrouter.resolve(req)is invoked after all filters have been processed.Although the current application always calls
setRouter()before processing requests, the defensive check improves API robustness for correct usage:Proposed fix
public FilterChainImpl createChain(HttpRequest request) { + if (router == null) { + throw new IllegalStateException("Router must be set before creating a filter chain"); + } List<Filter> filters = new ArrayList<>();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/org/juv25d/Pipeline.java` around lines 43 - 64, createChain() currently passes the field router into new FilterChainImpl without validating it's been initialized; add a defensive null-check (e.g. use Objects.requireNonNull(router, ...) or an explicit if (router == null) throw new IllegalStateException("router not set - call setRouter() before createChain()")) at the start of createChain() so that calling createChain() before setRouter() fails fast and clearly (references: createChain, setRouter, router, FilterChainImpl.doFilter).
| FROM eclipse-temurin:25-jre-alpine | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # might need to update this later when we have our explicit class names | ||
| COPY --from=build /app/target/app.jar app.jar | ||
| ENTRYPOINT ["java", "-jar", "app.jar"] |
There was a problem hiding this comment.
Container runs as root — add a non-root USER directive
No USER instruction is present in the runtime stage. The application process runs as root inside the container, meaning any exploit of the server grants root-level container access. Trivy rule DS-0002 flags this explicitly.
🔒 Proposed fix — add a non-root user
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
+RUN addgroup -S appgroup && adduser -S appuser -G appgroup
+USER appuser
+
# might need to update this later when we have our explicit class names
COPY --from=build /app/target/app.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Dockerfile` around lines 11 - 17, The Dockerfile runtime stage currently runs
the app as root; create and switch to a non-root user before ENTRYPOINT by
adding a user (e.g., group/user creation and UID/GID), chown the application
artifacts copied by COPY --from=build (app.jar) and set USER to that non-root
user after WORKDIR and before ENTRYPOINT so the java -jar app.jar process runs
unprivileged; ensure permissions allow the user to read/execute app.jar and the
WORKDIR.
| <dependency> | ||
| <groupId>org.testcontainers</groupId> | ||
| <artifactId>testcontainers</artifactId> | ||
| <version>2.0.3</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.testcontainers</groupId> | ||
| <artifactId>junit-jupiter</artifactId> | ||
| <version>1.21.4</version> | ||
| <scope>test</scope> | ||
| </dependency> |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Testcontainers 2.0.3 junit-jupiter module version compatibility
💡 Result:
For Testcontainers Java 2.0.3, the JUnit Jupiter integration module is version-aligned with core:
- Use
org.testcontainers:testcontainers-junit-jupiter:2.0.3together withorg.testcontainers:testcontainers:2.0.3(the 2.0.3 JUnit-Jupiter module depends on core 2.0.3). [1][2] - JUnit 5 itself is not brought in by that module’s POM, so you must add your own JUnit Jupiter (API/engine) dependencies (whatever JUnit 5 version your project uses). [1][3]
- If you’re migrating from 1.x: in 2.0.x, modules were renamed to the
testcontainers-*prefix (so oldorg.testcontainers:junit-jupiter→org.testcontainers:testcontainers-junit-jupiter). [4]
Recommended (Maven) approach: import the Testcontainers BOM at 2.0.3 and omit individual Testcontainers versions to avoid mismatches. [1]
Sources
[1] Maven Central – testcontainers-junit-jupiter:2.0.3 POM (dependency on testcontainers:2.0.3) (central.sonatype.com)
[2] Maven Central – testcontainers:2.0.3 POM (core artifact) (central.sonatype.com)
[3] Testcontainers docs – JUnit 5 (Jupiter) integration module overview (java.testcontainers.org)
[4] Testcontainers GitHub releases – 2.0.0 notes on module rename prefix testcontainers- (github.com)
Citations:
- 1: https://central.sonatype.com/artifact/org.testcontainers/testcontainers-junit-jupiter/2.0.3
- 2: https://central.sonatype.com/artifact/org.testcontainers/testcontainers/2.0.3
- 3: https://java.testcontainers.org/test_framework_integration/junit_5/?utm_source=openai
- 4: https://github.com/testcontainers/testcontainers-java/releases?utm_source=openai
Fix Testcontainers artifact ID and version mismatch.
The junit-jupiter module uses org.testcontainers:junit-jupiter:1.21.4, which is incompatible with core testcontainers:2.0.3. Testcontainers 2.0.x renamed modules to the testcontainers-* prefix and versions are no longer aligned with 1.x. Update to org.testcontainers:testcontainers-junit-jupiter:2.0.3 to match the core artifact.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pom.xml` around lines 49 - 60, The junit-jupiter dependency is using the
wrong artifactId and mismatched version; replace the dependency that currently
has <artifactId>junit-jupiter</artifactId> and <version>1.21.4</version> with
the Testcontainers 2.0.x module by changing the artifactId to
testcontainers-junit-jupiter and the version to 2.0.3 (i.e., use
org.testcontainers:testcontainers-junit-jupiter:2.0.3) so it matches the core
testcontainers artifact.
| } catch (IOException e) { | ||
| logger.log(Level.SEVERE, "Error while handling request", e); | ||
| } finally { |
There was a problem hiding this comment.
Exceptions are swallowed without sending an HTTP error response to the client.
When httpParser.parse(in) throws (e.g., malformed request), or the pipeline throws, the catch block only logs at SEVERE and lets the try-with-resources close the socket. The client receives an abrupt connection reset instead of a proper 400 Bad Request or 500 Internal Server Error. This makes clients unable to surface or retry intelligently, and breaks browser behavior on malformed requests.
A minimal fix differentiates parse errors (→ 400) from pipeline/write errors (→ 500):
🐛 Proposed fix
+import java.util.Map;
+import java.util.logging.Level;
`@Override`
public void run() {
String connectionId = java.util.UUID.randomUUID().toString().substring(0, 8);
org.juv25d.logging.LogContext.setConnectionId(connectionId);
try (socket) {
var in = socket.getInputStream();
var out = socket.getOutputStream();
+ HttpRequest parsed;
+ try {
+ parsed = httpParser.parse(in);
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "Malformed request from client", e);
+ HttpResponseWriter.write(out, new HttpResponse(400, "Bad Request", Map.of(), new byte[0]));
+ return;
+ }
- HttpRequest parsed = httpParser.parse(in);
String remoteIp = socket.getInetAddress().getHostAddress();
// ... rest unchanged🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/ConnectionHandler.java` around lines 59 - 61, The
current catch in ConnectionHandler swallows IOExceptions and closes the socket,
causing clients to get connection resets; update the exception handling around
httpParser.parse(in) and the pipeline/write sequence to send proper HTTP
responses: detect parse errors thrown by httpParser.parse(in) (e.g., a
HttpParseException or similar) and write a "400 Bad Request" response to the
socket output stream, while other IO/pipeline exceptions should result in a "500
Internal Server Error" response; ensure you write the response to the
OutputStream (and flush) inside its own try/catch so errors there are logged via
logger.log(Level.SEVERE, ...) but still let the try-with-resources close the
socket afterwards. Include references to httpParser.parse(in), the
pipeline/write path, and logger in your changes.
| public class DefaultConnectionHandlerFactory implements ConnectionHandlerFactory{ | ||
| private final HttpParser httpParser; | ||
| private final Logger logger; | ||
| private final Pipeline pipeline; | ||
|
|
||
| public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger, Pipeline pipeline) { | ||
| this.httpParser = httpParser; | ||
| this.logger = logger; | ||
| this.pipeline = pipeline; | ||
| } | ||
|
|
||
| @Override | ||
| public Runnable create(Socket socket, Pipeline pipeline) { | ||
| return new ConnectionHandler(socket, httpParser, logger, pipeline); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the ConnectionHandlerFactory interface
echo "=== ConnectionHandlerFactory Interface ==="
fd -t f '\.java$' | xargs rg -l "interface ConnectionHandlerFactory"
rg -A 10 "interface ConnectionHandlerFactory" --type=java
echo -e "\n=== Usages of handlerFactory.create() ==="
rg -n 'handlerFactory\.create\(' --type=java -B 2 -A 2
echo -e "\n=== Usages of \.create\(socket' ==="
rg -n '\.create\(socket' --type=java -B 2 -A 2
echo -e "\n=== How DefaultConnectionHandlerFactory is instantiated ==="
rg -n "new DefaultConnectionHandlerFactory" --type=java -B 2 -A 2Repository: ithsjava25/ithsjava25-java-project-webserver-maven-java-template
Length of output: 1817
Remove unused pipeline field—the create() method parameter shadows it.
The constructor stores this.pipeline, but create(Socket socket, Pipeline pipeline) receives its own pipeline parameter that shadows the field. The field is never read and should be removed along with its constructor parameter.
Refactoring
public class DefaultConnectionHandlerFactory implements ConnectionHandlerFactory{
private final HttpParser httpParser;
private final Logger logger;
- private final Pipeline pipeline;
- public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger, Pipeline pipeline) {
+ public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger) {
this.httpParser = httpParser;
this.logger = logger;
- this.pipeline = pipeline;
}
`@Override`
public Runnable create(Socket socket, Pipeline pipeline) {
return new ConnectionHandler(socket, httpParser, logger, pipeline);
}
}Update App.java line 59 accordingly: new DefaultConnectionHandlerFactory(httpParser, logger).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public class DefaultConnectionHandlerFactory implements ConnectionHandlerFactory{ | |
| private final HttpParser httpParser; | |
| private final Logger logger; | |
| private final Pipeline pipeline; | |
| public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger, Pipeline pipeline) { | |
| this.httpParser = httpParser; | |
| this.logger = logger; | |
| this.pipeline = pipeline; | |
| } | |
| @Override | |
| public Runnable create(Socket socket, Pipeline pipeline) { | |
| return new ConnectionHandler(socket, httpParser, logger, pipeline); | |
| } | |
| public class DefaultConnectionHandlerFactory implements ConnectionHandlerFactory{ | |
| private final HttpParser httpParser; | |
| private final Logger logger; | |
| public DefaultConnectionHandlerFactory(HttpParser httpParser, Logger logger) { | |
| this.httpParser = httpParser; | |
| this.logger = logger; | |
| } | |
| `@Override` | |
| public Runnable create(Socket socket, Pipeline pipeline) { | |
| return new ConnectionHandler(socket, httpParser, logger, pipeline); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/DefaultConnectionHandlerFactory.java` around lines 8
- 22, Remove the unused Pipeline field and constructor parameter from
DefaultConnectionHandlerFactory: delete the private final Pipeline pipeline
field, remove the Pipeline parameter from the constructor signature and body so
the constructor only takes (HttpParser httpParser, Logger logger) and stores
those two fields, and ensure create(Socket socket, Pipeline pipeline) continues
to use its method parameter when instantiating ConnectionHandler; update call
sites (e.g., the instantiation in App.java) to call new
DefaultConnectionHandlerFactory(httpParser, logger).
| public IpFilter(Set<String> whitelist, Set<String> blacklist) { | ||
| this.whitelist = whitelist; | ||
| this.blacklist = blacklist; | ||
| } |
There was a problem hiding this comment.
Defensively copy whitelist/blacklist to prevent external mutation and concurrent-modification hazards.
The constructor stores live references to the caller-supplied Set instances. Two problems:
- Security correctness: the caller can add or remove IPs after construction, silently changing the filter's access-control rules.
- Thread safety:
HashSetis not thread-safe. If the caller mutates the set from another thread whiledoFilterreads it, aConcurrentModificationExceptioncan occur.
🛡️ Proposed fix – take immutable snapshots at construction time
public IpFilter(Set<String> whitelist, Set<String> blacklist) {
- this.whitelist = whitelist;
- this.blacklist = blacklist;
+ this.whitelist = whitelist != null ? Set.copyOf(whitelist) : null;
+ this.blacklist = blacklist != null ? Set.copyOf(blacklist) : null;
}Set.copyOf() (Java 10+) creates an immutable, null-element-rejecting snapshot that is safe for concurrent reads with no synchronization overhead.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public IpFilter(Set<String> whitelist, Set<String> blacklist) { | |
| this.whitelist = whitelist; | |
| this.blacklist = blacklist; | |
| } | |
| public IpFilter(Set<String> whitelist, Set<String> blacklist) { | |
| this.whitelist = whitelist != null ? Set.copyOf(whitelist) : null; | |
| this.blacklist = blacklist != null ? Set.copyOf(blacklist) : null; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/filter/IpFilter.java` around lines 15 - 18, The
constructor for IpFilter currently stores caller-supplied Set references
(whitelist/blacklist), enabling external mutation and possible
ConcurrentModificationExceptions in doFilter; change the IpFilter(Set<String>
whitelist, Set<String> blacklist) constructor to defensively copy both inputs
into immutable snapshot sets (e.g., use Set.copyOf(...) or new HashSet<>(...)
wrapped/converted to an unmodifiable set) and assign those copies to the class
fields so doFilter reads a stable, thread-safe snapshot rather than live mutable
collections.
| private static HttpResponse createNotFoundResponse(String path) { | ||
| String html = """ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <title>404 Not Found</title> | ||
| <style> | ||
| body { | ||
| font-family: Arial, sans-serif; | ||
| max-width: 600px; | ||
| margin: 100px auto; | ||
| text-align: center; | ||
| } | ||
| h1 { color: #e74c3c; } | ||
| p { color: #666; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>404 - Not Found</h1> | ||
| <p>The requested resource <code>%s</code> was not found on this server.</p> | ||
| </body> | ||
| </html> | ||
| """.formatted(path); | ||
|
|
||
| Map<String, String> headers = new HashMap<>(); | ||
| headers.put("Content-Type", "text/html; charset=utf-8"); | ||
|
|
||
| return new HttpResponse(404, "Not Found", headers, html.getBytes(StandardCharsets.UTF_8)); |
There was a problem hiding this comment.
XSS: user-supplied path is embedded into HTML without escaping.
A request to /<script>alert(document.cookie)</script> passes isPathSafe (starts with /, no ..////\) and is injected verbatim into the <code> element in the 404 response. Browsers will execute the injected script.
🛡️ Proposed fix — HTML-escape the path before embedding
private static HttpResponse createNotFoundResponse(String path) {
+ String safePath = path
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("'", "'");
+
String html = """
...
<p>The requested resource <code>%s</code> was not found on this server.</p>
...
- """.formatted(path);
+ """.formatted(safePath);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/handler/StaticFileHandler.java` around lines 151 -
179, createNotFoundResponse embeds the user-supplied path into HTML without
escaping, allowing XSS; fix by HTML-escaping the path before formatting it into
the response (e.g. add or reuse a helper like escapeHtml(String) in
StaticFileHandler that replaces &, <, >, ", ' and forward-slash as needed) and
use the escaped value instead of the raw path when building the HTML string and
HttpResponse; keep the Content-Type header and UTF-8 byte conversion unchanged
and ensure the helper is invoked where createNotFoundResponse formats the HTML.
| byte[] body = new byte[0]; | ||
| if (headers.containsKey(CONTENT_LENGTH)) { | ||
| int length; | ||
| try { | ||
| length = Integer.parseInt(headers.get(CONTENT_LENGTH)); | ||
| } catch (NumberFormatException e) { | ||
| throw new IOException("Invalid Content-Length: " + headers.get(CONTENT_LENGTH), e); | ||
| } | ||
| if (length < 0) { | ||
| throw new IOException("Negative Content-Length: " + length); | ||
| } | ||
| body = in.readNBytes(length); | ||
| } |
There was a problem hiding this comment.
No upper bound on Content-Length — potential OOM/DoS vector.
A malicious client can send Content-Length: 2147483647 and readNBytes will attempt to allocate a ~2GB byte array, causing OutOfMemoryError and taking down the server. Add a maximum body size check.
Proposed fix
+ private static final int MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB
+
byte[] body = new byte[0];
if (headers.containsKey(CONTENT_LENGTH)) {
int length;
try {
length = Integer.parseInt(headers.get(CONTENT_LENGTH));
} catch (NumberFormatException e) {
throw new IOException("Invalid Content-Length: " + headers.get(CONTENT_LENGTH), e);
}
if (length < 0) {
throw new IOException("Negative Content-Length: " + length);
}
+ if (length > MAX_BODY_SIZE) {
+ throw new IOException("Content-Length exceeds maximum allowed size: " + length);
+ }
body = in.readNBytes(length);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/http/HttpParser.java` around lines 50 - 62, The
Content-Length parsing in HttpParser currently allocates body =
in.readNBytes(length) without any upper bound; add a max allowed body size
constant (e.g., MAX_BODY_SIZE) and after parsing length in the block that
handles CONTENT_LENGTH verify length <= MAX_BODY_SIZE (and keep the existing
negative check), otherwise throw an IOException indicating the body is too
large; only call in.readNBytes(length) when the length is within bounds so the
server cannot OOM or be DOSed by large Content-Length values.
| String levelName = System.getProperty( | ||
| "log.level", | ||
| System.getenv().getOrDefault("LOG_LEVEL", ConfigLoader.getInstance().getLogLevel()) | ||
| ); | ||
|
|
||
| try { | ||
| Level level = Level.parse(levelName.toUpperCase()); | ||
| logger.setLevel(level); | ||
| } catch (IllegalArgumentException e) { | ||
| logger.setLevel(Level.INFO); | ||
| logger.warning("Invalid log level: '" + levelName + "', defaulting to INFO"); | ||
| } |
There was a problem hiding this comment.
NPE crashes server startup when ConfigLoader.getLogLevel() returns null.
ConfigLoader.logLevel is only assigned inside if (loggingConfig != null), so it returns null when the logging section is absent from application-properties.yml. The call chain System.getenv().getOrDefault("LOG_LEVEL", null) then System.getProperty("log.level", null) can both propagate that null, and null.toUpperCase() on line 33 throws a NullPointerException. The catch block only handles IllegalArgumentException, so the NPE propagates as an ExceptionInInitializerError, preventing the server from starting.
🛡️ Proposed fix
String levelName = System.getProperty(
"log.level",
- System.getenv().getOrDefault("LOG_LEVEL", ConfigLoader.getInstance().getLogLevel())
+ System.getenv().getOrDefault("LOG_LEVEL",
+ Optional.ofNullable(ConfigLoader.getInstance().getLogLevel()).orElse("INFO"))
);Add import java.util.Optional; at the top of the file.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| String levelName = System.getProperty( | |
| "log.level", | |
| System.getenv().getOrDefault("LOG_LEVEL", ConfigLoader.getInstance().getLogLevel()) | |
| ); | |
| try { | |
| Level level = Level.parse(levelName.toUpperCase()); | |
| logger.setLevel(level); | |
| } catch (IllegalArgumentException e) { | |
| logger.setLevel(Level.INFO); | |
| logger.warning("Invalid log level: '" + levelName + "', defaulting to INFO"); | |
| } | |
| String levelName = System.getProperty( | |
| "log.level", | |
| System.getenv().getOrDefault("LOG_LEVEL", | |
| Optional.ofNullable(ConfigLoader.getInstance().getLogLevel()).orElse("INFO")) | |
| ); | |
| try { | |
| Level level = Level.parse(levelName.toUpperCase()); | |
| logger.setLevel(level); | |
| } catch (IllegalArgumentException e) { | |
| logger.setLevel(Level.INFO); | |
| logger.warning("Invalid log level: '" + levelName + "', defaulting to INFO"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/logging/ServerLogging.java` around lines 27 - 38,
levelName can be null (from ConfigLoader.getLogLevel()) which leads to a
NullPointerException when calling toUpperCase(); change the logic that computes
levelName (the variable used by Level.parse in ServerLogging) to provide a
non-null fallback (e.g., "INFO") before calling toUpperCase()/Level.parse, or
wrap the value with Optional/ternary check and only call toUpperCase() when
non-null; ensure the try/catch still handles IllegalArgumentException from
Level.parse and that logger.setLevel is called with the fallback Level.INFO when
input is missing or invalid.
| } catch (Exception e) { | ||
| throw new RuntimeException("Failed to load application config"); | ||
| } |
There was a problem hiding this comment.
Exception cause is swallowed, and the file-not-found IllegalArgumentException message is silently replaced.
Two related problems:
-
throw new RuntimeException("Failed to load application config")drops the root-cause exceptione. When aClassCastExceptionorIOExceptionoccurs during YAML parsing, the stack trace is completely lost, making debugging very hard. -
The
IllegalArgumentExceptionthrown at line 33 ("Did not find application-properties.yml") is thrown inside thetryblock, socatch (Exception e)catches it, and the specific diagnostic message is replaced by the generic "Failed to load application config" string.
🐛 Proposed fix
- } catch (Exception e) {
- throw new RuntimeException("Failed to load application config");
- }
+ } catch (IllegalArgumentException e) {
+ throw e; // re-throw "Did not find application-properties.yml" as-is
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load application config", e);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (Exception e) { | |
| throw new RuntimeException("Failed to load application config"); | |
| } | |
| } catch (IllegalArgumentException e) { | |
| throw e; // re-throw "Did not find application-properties.yml" as-is | |
| } catch (Exception e) { | |
| throw new RuntimeException("Failed to load application config", e); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/org/juv25d/util/ConfigLoader.java` around lines 62 - 64, In
ConfigLoader, preserve the original exception and avoid swallowing the specific
IllegalArgumentException from the missing file check: update the catch(Exception
e) in the config-loading method to rethrow a RuntimeException that includes e as
the cause (e.g., new RuntimeException("Failed to load application config", e)),
and ensure the earlier IllegalArgumentException thrown with message "Did not
find application-properties.yml" is not captured and replaced (either move that
file-existence check outside the try block or rethrow IllegalArgumentException
immediately if caught).
| window.addEventListener("popstate", () => { | ||
| navigate(window.location.pathname); | ||
| }); |
There was a problem hiding this comment.
popstate → navigate → pushState creates duplicate history entries, breaking back/forward navigation.
When the user presses Back, popstate fires and calls navigate(), which calls history.pushState() on line 54. This pushes a new entry instead of restoring the old one, making the back button effectively non-functional (infinite loop between two states).
Use history.replaceState in the popstate path, or split the logic so popstate doesn't push:
Proposed fix
window.addEventListener("popstate", () => {
- navigate(window.location.pathname);
+ navigateTo(window.location.pathname, false);
});
-function navigate(href) {
+function navigate(href) {
+ navigateTo(href, true);
+}
+
+function navigateTo(href, pushHistory) {
nav.classList.add("disable-anchors");
const main = document.getElementById("main-content");
main.classList.add("fade-out");
setTimeout(() => {
fetch(href)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
})
.then(html => {
const doc = new DOMParser().parseFromString(html, "text/html");
const newMain = doc.querySelector("main");
if (!newMain) throw new Error("No <main> found in " + href);
main.innerHTML = newMain.innerHTML;
- history.pushState(null, "", href);
+ if (pushHistory) history.pushState(null, "", href);
route(href);
main.classList.remove("fade-out");
setTimeout(() => {
nav.classList.remove("disable-anchors");
}, 150);
})
.catch(err => {
console.error("Navigation failed:", err);
main.classList.remove("fade-out");
nav.classList.remove("disable-anchors");
});
}, 200);
}Also applies to: 54-54
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/resources/static/js/app.js` around lines 16 - 18, The popstate
handler calls navigate(window.location.pathname) which then calls
history.pushState (in navigate) and creates duplicate history entries; modify
the logic so that when navigate is invoked from the popstate handler it does not
call pushState—either call history.replaceState inside the popstate path or add
a flag/parameter to navigate (e.g., navigate(path, {fromPopState: true})) and
have navigate skip pushState and use replaceState when that flag is set,
ensuring pushState remains for user-initiated navigations only.
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores